package org.yzhzc.gateway.filter;

import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson2.JSON;
import org.yzhzc.common.core.constants.HttpConstants;
import org.yzhzc.common.core.domain.LoginUser;
import org.yzhzc.common.core.domain.R;
import org.yzhzc.common.core.enums.ResultCode;
import org.yzhzc.common.core.enums.UserIdentity;
import org.yzhzc.common.core.utils.JwtUtils;
import org.yzhzc.common.security.service.TokenService;
import org.yzhzc.gateway.properties.IgnoreWhiteProperties;
import io.jsonwebtoken.Claims;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.CollectionUtils;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.util.List;

/**
 * 网关鉴权过滤器，负责处理请求的身份验证逻辑。
 */
@Slf4j
@Component
public class AuthFilter implements GlobalFilter, Ordered {

    @Value("${jwt.secret}") // 从配置文件中注入 JWT 密钥
    private String secret;

    // 排除过滤的 URI 白名单地址
    @Autowired
    private IgnoreWhiteProperties ignoreWhite;

    @Autowired
    private TokenService tokenService; // 注入 TokenService，用于处理 token 的相关操作

    @Override
    public int getOrder() {
        return -200; // 设置过滤器执行顺序，值越小，优先级越高
    }

    /**
     * 过滤器核心方法，处理请求的过滤逻辑
     * @param exchange 请求和响应的交换对象
     * @param chain 过滤链，允许调用下一个过滤器
     * @return Mono<Void> 返回异步处理结果
     */
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        String url = request.getURI().getPath(); // 获取请求的接口地址

        // 判断当前 URL 是否在白名单中，如果是则跳过 token 校验
        if (matches(url, ignoreWhite.getWhites())) {
            return chain.filter(exchange); // 如果在白名单中，直接放行
        }

        // 获取请求中的 token
        String token = this.getToken(request);
        if (StrUtil.isEmpty(token)) {
            return unauthorizedResponse(exchange, "令牌不能为空"); // token 不能为空
        }

        Claims claims = null;
        try {
            claims = tokenService.getClaims(token, secret); // 解析 token 获取 payload 信息
            if (claims == null) {
                return unauthorizedResponse(exchange, "令牌已过期或验证不正确！"); // token 解析失败
            }
        } catch (Exception e) {
            return unauthorizedResponse(exchange, "令牌已过期或验证不正确！"); // 捕获解析异常
        }

        // 验证 token 是否有效
        if (!tokenService.isTokenValid(claims)) {
            return unauthorizedResponse(exchange, "登录状态已过期"); // 登录状态已过期
        }

        Long userId = tokenService.getUserId(claims); // 从 claims 中获取用户 ID
        if (userId == null) {
            return unauthorizedResponse(exchange, "令牌验证失败"); // 用户 ID 不能为空
        }

        // 获取用户信息，判断用户身份和请求 URL 是否匹配
        LoginUser user = tokenService.getLoginUser(claims);
        if ((url.contains(HttpConstants.SYSTEM_URL_PREFIX) && !UserIdentity.ADMIN.getValue().equals(user.getIdentity())) ||
                (url.contains(HttpConstants.FRIEND_URL_PREFIX) && !UserIdentity.ORDINARY.getValue().equals(user.getIdentity()))) {
            return unauthorizedResponse(exchange, "令牌验证失败"); // 身份不匹配
        }

        return chain.filter(exchange); // 通过身份验证，继续请求处理
    }

    /**
     * 查找指定 URL 是否匹配指定匹配规则链表中的任意一个字符串
     *
     * @param url         指定 URL
     * @param patternList 需要检查的匹配规则链表
     * @return 是否匹配
     */
    private boolean matches(String url, List<String> patternList) {
        if (StrUtil.isEmpty(url) || CollectionUtils.isEmpty(patternList)) {
            return false; // URL 为空或匹配规则链表为空，返回 false
        }
        for (String pattern : patternList) {
            if (isMatch(pattern, url)) {
                return true; // 如果有匹配，返回 true
            }
        }
        return false; // 所有匹配规则均未匹配，返回 false
    }

    /**
     * 判断 URL 是否与规则匹配
     * 匹配规则中：
     * pattern 中可以写一些特殊字符
     * ? 表示单个任意字符;
     * * 表示一层路径内的任意字符串，不可跨层级;
     * ** 表示任意层路径;
     *
     * @param pattern 匹配规则
     * @param url     需要匹配的 URL
     * @return 是否匹配
     */
    private boolean isMatch(String pattern, String url) {
        AntPathMatcher matcher = new AntPathMatcher();
        return matcher.match(pattern, url); // 使用 AntPathMatcher 进行匹配
    }

    /**
     * 从请求头中获取请求 token
     *
     * @param request HTTP 请求
     * @return token
     */
    private String getToken(ServerHttpRequest request) {
        String token = request.getHeaders().getFirst(HttpConstants.AUTHENTICATION);
        // 如果前端设置了令牌前缀，则裁剪掉前缀
        if (StrUtil.isNotEmpty(token) && token.startsWith(HttpConstants.PREFIX)) {
            token = token.replaceFirst(HttpConstants.PREFIX, StrUtil.EMPTY);
        }
        return token; // 返回处理后的 token
    }

    /**
     * 构建未授权响应
     *
     * @param exchange 服务器交换信息
     * @param msg      返回消息
     * @return Mono<Void>
     */
    private Mono<Void> unauthorizedResponse(ServerWebExchange exchange, String msg) {
        log.error("[鉴权异常处理]请求路径:{}", exchange.getRequest().getPath());
        return webFluxResponseWriter(exchange.getResponse(), msg, ResultCode.FAILED_UNAUTHORIZED.getCode());
    }

    /**
     * 拼装 WebFlux 模型响应
     *
     * @param response HTTP 响应对象
     * @param msg      返回的消息，通常是描述错误或状态的文本
     * @param code     返回的状态码，表示响应的具体状态，如错误代码或成功标识
     * @return Mono<Void> 返回一个 Mono 对象，表示异步响应操作的完成
     */
    private Mono<Void> webFluxResponseWriter(ServerHttpResponse response, String msg, int code) {
        // 设置响应状态为 HTTP 200 OK，表示请求已成功处理
        response.setStatusCode(HttpStatus.OK);

        // 设置响应头的 Content-Type 为 application/json，表示返回的内容是 JSON 格式
        response.getHeaders().add(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE);

        // 创建一个失败的响应结果，其中 code 是状态码，msg 是消息内容
        R<?> result = R.fail(code, msg);

        // 将 result 对象转换为 JSON 字符串并包装为字节数组
        DataBuffer dataBuffer = response.bufferFactory().wrap(JSON.toJSONString(result).getBytes());

        // 将字节数组写入响应体并返回 Mono 对象，表示响应的写入操作
        return response.writeWith(Mono.just(dataBuffer));
    }
}
